// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Lexer2.cpp
 *
 *  Created on: Dec 25, 2009
 *      Author: dhovemey
 */

#include <cwctype>
#include <cassert>
#include <string>
#include <map>
#include <set>
#include "util/Reader.h"
#include "util/PushbackReader.h"
#include "util/String.h"
#include "util/Error.h"
class Token2;
class ASTNode;
#include "fe/LexicalAnalyzerException.h"
#include "fe/Token2.h"
#include "fe/Lexer2.h"

typedef std::map<std::wstring, Token2::TokenType> KeywordMap;
static KeywordMap s_keywordMap;

typedef std::map<wchar_t, Token2::TokenType> SingleCharacterTokenMap;
static SingleCharacterTokenMap s_singleCharacterTokenMap;

static SingleCharacterTokenMap s_arithOpTokenMap;

typedef std::set<wchar_t> EscapeSequenceMap;
static EscapeSequenceMap s_escapeSequenceMap;

struct InitLexerMaps {
public:
	InitLexerMaps() {
		s_keywordMap[L"method"] = Token2::METHOD;
		s_keywordMap[L"field"] = Token2::FIELD;
		s_keywordMap[L"class"] = Token2::CLASS;
		s_keywordMap[L"new"] = Token2::NEW;
		s_keywordMap[L"self"] = Token2::SELF;
		s_keywordMap[L"var"] = Token2::VAR;
		s_keywordMap[L"while"] = Token2::WHILE;
		s_keywordMap[L"if"] = Token2::IF;
		s_keywordMap[L"else"] = Token2::ELSE;
		s_keywordMap[L"import"] = Token2::IMPORT;
		s_keywordMap[L"as"] = Token2::AS;
		s_keywordMap[L"module"] = Token2::MODULE;
		s_keywordMap[L"return"] = Token2::RETURN;
		s_keywordMap[L"nil"] = Token2::NIL;
		s_keywordMap[L"magic"] = Token2::MAGIC;
		s_keywordMap[L"true"] = Token2::TRUE;
		s_keywordMap[L"false"] = Token2::FALSE;

		s_singleCharacterTokenMap[L'.'] = Token2::DOT;
		s_singleCharacterTokenMap[L','] = Token2::COMMA;
		s_singleCharacterTokenMap[L';'] = Token2::SEMICOLON;
		s_singleCharacterTokenMap[L'('] = Token2::LPAREN;
		s_singleCharacterTokenMap[L')'] = Token2::RPAREN;
		s_singleCharacterTokenMap[L'{'] = Token2::LBRACE;
		s_singleCharacterTokenMap[L'}'] = Token2::RBRACE;

		s_arithOpTokenMap[L'+'] = Token2::PLUS;
		s_arithOpTokenMap[L'-'] = Token2::MINUS;
		s_arithOpTokenMap[L'*'] = Token2::TIMES;
		s_arithOpTokenMap[L'/'] = Token2::DIVIDE;

		s_escapeSequenceMap.insert(L'n');
		s_escapeSequenceMap.insert(L't');
		s_escapeSequenceMap.insert(L'f');
		s_escapeSequenceMap.insert(L'b');
		s_escapeSequenceMap.insert(L'\\');
		s_escapeSequenceMap.insert(L'"');
	}
};

static InitLexerMaps s_initLexerMaps;

static bool contIdentOrKW(wchar_t c)
{
	return iswalnum(c) || c == L'_';
}

static Token2::TokenType tokTypeIdentOrKW(const std::wstring &lexeme)
{
	KeywordMap::iterator i = s_keywordMap.find(lexeme);
	if (i == s_keywordMap.end()) {
		return Token2::IDENT;
	} else {
		return (*i).second;
	}
}

static bool contIntLiteral(wchar_t c)
{
	return iswdigit(c);
}

static Token2::TokenType tokTypeIntLiteral(const std::wstring &lexeme)
{
	return Token2::INT_LITERAL;
}

static Token2::TokenType tokTypeSymbolLiteral(const std::wstring &lexeme)
{
	return Token2::SYMBOL_LITERAL;
}

static Token2::TokenType relTokType(wchar_t c) {
	switch (c) {
	case L'<':
		return Token2::LESS_THAN;
	case L'>':
		return Token2::GREATER_THAN;
	case L'!':
		return Token2::NOT;
	default:
		assert(false);
		return Token2::INVALID_TOKEN_TYPE;
	}
}

static Token2::TokenType relEqTokType(wchar_t c) {
	switch (c) {
	case L'<':
		return Token2::LESS_THAN_EQ;
	case L'>':
		return Token2::GREATER_THAN_EQ;
	case L'!':
		return Token2::NOT_EQUALS;
	default:
		assert(false);
		return Token2::INVALID_TOKEN_TYPE;
	}
}

Lexer2::Lexer2(Reader *readerToAdopt)
	: m_reader(new PushbackReader(readerToAdopt))
	, m_prevToken(0)
	, m_nextToken(0)
	, m_curLine(1)
{

}

Lexer2::~Lexer2()
{
	delete m_reader;

	// TODO: delete tokens
}

bool Lexer2::next(Token2 *&tokPtr)
{
	if (m_nextToken != 0) {
		tokPtr = m_nextToken;
		return true;
	}

	long c_ = readNextNonWS();
	if (c_ < 0) {
		tokPtr = 0;
		return true;
	}
	wchar_t c = wchar_t(c_);

	Location startLoc(getCurrentLocation());

	if (iswalpha(c) || c == L'_') {
		readToken(c, &contIdentOrKW, startLoc, &tokTypeIdentOrKW);
	} else if (iswdigit(c)) {
		readToken(c, &contIntLiteral, startLoc, &tokTypeIntLiteral);
	} else if (c == L'"') {
		if (!readStringLiteral(c, startLoc)) {
			return false;
		}
	} else if (c == L':') {
		c_ = read();
		if (c_ < 0) {
			// colon was followed by end of input: just return a COLON token
			createToken(Token2::COLON, L":", startLoc);
		} else {
			c = wchar_t(c_);
			if (c == L':') {
				// colon was followed by another colon, so it's a DOUBLECOLON token
				createToken(Token2::DOUBLECOLON, L"::", startLoc);
			} else if (iswalpha(c)) {
				// colon was followed by an identifier start character, so
				// it's a SYMBOL_LITERAL token
				pushBack(c);
				readToken(L':', &contIdentOrKW, startLoc, &tokTypeSymbolLiteral);
			} else {
				// it's not a DOUBLECOLON or a SYMBOL_LITERAL, so just
				// complete the COLON token
				pushBack(c);
				createToken(Token2::COLON, L":", startLoc);
			}
		}
	} else if (s_singleCharacterTokenMap.find(c) != s_singleCharacterTokenMap.end()) {
		// a single-character token
		createToken(s_singleCharacterTokenMap[c], std::wstring(1, c), startLoc);
	} else if (c == L'<' || c == L'>' || c == L'!') {
		// relational operator
		c_ = read();
		if (c_ >= 0 && c_ == L'=') {
			// this is the "equals" version of the relational operator: <=, >=, !=
			std::wstring lexeme(1, c);
			lexeme.append(1, L'=');
			createToken(relEqTokType(c), lexeme, startLoc);
		} else {
			// some other character, or end-of-input, occurred after the relational operator
			if (c_ > 0) {
				pushBack(wchar_t(c_));
			}
			createToken(relTokType(c), std::wstring(1, c), startLoc);
		}
	} else if (c == L'=') {
		// assignment or equality operator
		c_ = read();
		if (c_ >= 0 && c_ == L'=') {
			// equality operator
			createToken(Token2::EQUALS, L"==", startLoc);
		} else {
			// some other character, or end-of-input, occurred after the assignment operator
			if (c_ >= 0) {
				pushBack(wchar_t(c_));
			}
			createToken(Token2::ASSIGN, std::wstring(L"="), startLoc);
		}
	} else if (c == L'&' || c == L'|') {
		// TODO: support bitwise and/or
		long c2 = read();
		if (c2 < 0) {
			Error::raise(new LexicalAnalyzerException(L"Unexpected end of input", getCurrentLocation()));
			return false;
		}
		if (wchar_t(c2) != c) {
			Error::raise(new LexicalAnalyzerException(L"Unexpected character following " + std::wstring(c, 1), getCurrentLocation()));
			return false;
		}
		createToken(c == '&' ? Token2::LOGICAL_AND : Token2::LOGICAL_OR, c == '&' ? L"&&" : L"||", startLoc);
	} else if (s_arithOpTokenMap.find(c) != s_arithOpTokenMap.end()) {
		bool negativeIntegerLiteral = false;

		// special case: if c is '-', it could be a negative integer literal
		if (c == '-') {
			long c2 = read();
			if (c2 >= 0 && iswdigit(wchar_t(c2))) {
				pushBack(wchar_t(c2));
				readToken(c, &contIntLiteral, startLoc, &tokTypeIntLiteral);
				negativeIntegerLiteral = true;
			} else if (c2 >= 0) {
				pushBack(wchar_t(c2));
			}
		}

		if (!negativeIntegerLiteral) {
			// arithmetic operator
			// TODO: allow compound assignment operators, e.g.
			//     a += b;
			// which should be considered equivalent to
			//     a = a + b;
			createToken(s_arithOpTokenMap[c], std::wstring(1, c), startLoc);
		}
	} else {
		//throw LexicalAnalyzerException(std::wstring(L"unexpected character ") + wchar_t(c), startLoc);
		Error::raise(new LexicalAnalyzerException(std::wstring(L"unexpected character ") + wchar_t(c), startLoc));
	}

	tokPtr = m_nextToken;
	return true;

}

void Lexer2::consume()
{
	assert(m_nextToken != 0);
	m_nextToken = 0;
}

Location Lexer2::getCurrentLocation() const
{
	return Location(m_curLine);
}

long Lexer2::read()
{
	long c = m_reader->readChar();

	if (c == L'\n') {
		m_curLine++;
	}

	return c;
}

long Lexer2::readNextNonWS()
{
	long c;

	while (true) {
		c = read();
		if (c < 0) {
			return -1;
		}

		if (wchar_t(c) == L'#') {
			skipComment();
			continue;
		}

		if (!iswspace(wchar_t(c))) {
			return c;
		}
	}
}

void Lexer2::skipComment()
{
	long c;

	while (true) {
		c = read();
		if (c < 0 || wchar_t(c) == L'\n') {
			return;
		}
	}
}

void Lexer2::pushBack(wchar_t c)
{
	if (c == L'\n') {
		m_curLine--;
	}
	m_reader->pushBack(c);
}

void Lexer2::readToken(
	wchar_t first,
	bool (*validContinuation)(wchar_t),
	const Location &startLoc,
	Token2::TokenType (*chooseType)(const std::wstring &lexeme))
{
	std::wstring lexeme;

	lexeme.append(1, first);

	while (true) {
		long c_ = read();
		if (c_ < 0) {
			break;
		}
		wchar_t c(c_);
		if (!validContinuation(c)) {
			pushBack(c);
			break;
		}
		lexeme.append(1, c);
	}

	createToken(chooseType(lexeme), lexeme, startLoc);
}

bool Lexer2::readStringLiteral(wchar_t first, const Location &startLoc)
{
	std::wstring lexeme;

	lexeme.append(1, first);

	while (true) {
		long c_ = read();

		if (c_ < 0) {
			Error::raise(new LexicalAnalyzerException(L"Unexpected end of input in string literal", getCurrentLocation()));
			return false;
		}
		wchar_t c(c_);

		if (c == L'"') {
			lexeme.append(1, c);
			break;
		}

		if (c == L'\\') {
			// handle escape sequences
			c_ = read();
			if (c_ < 0) {
				Error::raise(new LexicalAnalyzerException(L"Unexpected end of input in string literal", getCurrentLocation()));
				return false;
			}
			c = wchar_t(c_);
			EscapeSequenceMap::const_iterator i = s_escapeSequenceMap.find(c);
			if (i == s_escapeSequenceMap.end()) {
				Error::raise(new LexicalAnalyzerException(L"unrecognized escape character in string literal: " + std::wstring(1, c), startLoc));
			}
			lexeme.append(1, L'\\');
			lexeme.append(1, c);
		} else {
			lexeme.append(1, c);
		}
	}

	createToken(Token2::STRING_LITERAL, lexeme, startLoc);
	return true;
}

void Lexer2::createToken(Token2::TokenType type, const std::wstring &lexeme, const Location &loc)
{
	m_nextToken = new Token2(type, lexeme, loc, m_prevToken);
	m_prevToken = m_nextToken;
}
